﻿using System;
using System.Collections.Generic;
using System.Threading;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the operation result.
    /// </summary>
    public class OperationResult : IOperationResult
    {
        #region Fields

        /// <summary>
        /// Gets the wait handle of the current operation.
        /// </summary>
        protected static readonly WaitHandle WaitHandle;
        private readonly List<Action<IOperationResult>> _waiters;
        private OperationStatus _status;
        private object _result;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        static OperationResult()
        {
            WaitHandle = new ManualResetEvent(true);
            CompletedResult = new OperationResult(result: null);
        }


        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected OperationResult()
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected OperationResult(object result)
        {
            _status = OperationStatus.Success;
            Result = result;
            AsyncWaitHandle = WaitHandle;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected OperationResult(Exception exception)
        {
            _status = OperationStatus.Faulted;
            Exception = exception;
            AsyncWaitHandle = WaitHandle;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected OperationResult(Func<object, object> action, object asyncState, ExecutionType executionType,
            IThreadManager threadManager)
        {
            Should.NotBeNull(action, "action");
            _waiters = new List<Action<IOperationResult>>();
            _status = OperationStatus.Running;
            AsyncState = asyncState;
            BeginInvoke(action, executionType, threadManager);
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the completed result.
        /// </summary>
        public static IOperationResult CompletedResult { get; private set; }

        /// <summary>
        ///     Gets the result value of this operation.
        /// </summary>
        protected object Result
        {
            get
            {
                if (Exception != null)
                    throw Exception;
                if (!IsCompleted)
                    WaitHandle.WaitOne();
                return _result;
            }
            set { _result = value; }
        }

        #endregion

        #region Implementation of IOperationResult

        /// <summary>
        ///     Gets a value that indicates whether the asynchronous operation has completed.
        /// </summary>
        /// <returns>
        ///     true if the operation is complete; otherwise, false.
        /// </returns>
        public bool IsCompleted
        {
            get { return Status == OperationStatus.Faulted || Status == OperationStatus.Success; }
        }

        /// <summary>
        ///     Gets a <see cref="T:System.Threading.WaitHandle" /> that is used to wait for an asynchronous operation to complete.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Threading.WaitHandle" /> that is used to wait for an asynchronous operation to complete.
        /// </returns>
        public WaitHandle AsyncWaitHandle { get; protected set; }

        /// <summary>
        ///     Gets a user-defined object that qualifies or contains information about an asynchronous operation.
        /// </summary>
        /// <returns>
        ///     A user-defined object that qualifies or contains information about an asynchronous operation.
        /// </returns>
        public object AsyncState { get; protected set; }

        /// <summary>
        ///     Gets a value that indicates whether the asynchronous operation completed synchronously.
        /// </summary>
        /// <returns>
        ///     true if the asynchronous operation completed synchronously; otherwise, false.
        /// </returns>
        bool IAsyncResult.CompletedSynchronously
        {
            get { return false; }
        }

        /// <summary>
        ///     Gets whether the operation completed due to an unhandled exception.
        /// </summary>
        /// <returns>
        ///     true if the task has thrown an unhandled exception; otherwise false.
        /// </returns>
        public bool IsFaulted
        {
            get { return Status == OperationStatus.Faulted; }
        }

        /// <summary>
        ///     Gets the exception that caused the operation to end prematurely. If the operation completed successfully or has not
        ///     yet thrown any exceptions, this will return null.
        /// </summary>
        public Exception Exception { get; protected set; }

        /// <summary>
        ///     Gets the <see cref="OperationStatus" /> of this operation.
        /// </summary>
        public OperationStatus Status
        {
            get { return _status; }
            protected set
            {
                if (_waiters != null)
                {
                    using (DeadLockDetector.Lock(_waiters))
                    {
                        _status = value;
                    }
                    if (IsCompleted)
                        _waiters.ForEach(action => action(this));
                }
                else
                    _status = value;
            }
        }

        /// <summary>
        ///     Begins wait for the operation to complete execution.
        /// </summary>
        /// <param name="callback">The specified callback.</param>
        public void BeginWait(Action<IOperationResult> callback)
        {
            if (_waiters == null)
            {
                callback(this);
                return;
            }
            using (DeadLockDetector.Lock(_waiters))
            {
                if (!IsCompleted)
                {
                    _waiters.Add(callback);
                    return;
                }
            }
            callback(this);
        }

        #endregion

        #region Methods

        private void BeginInvoke(Func<object, object> action, ExecutionType executionType, IThreadManager threadManager)
        {
            var resetEvent = new ManualResetEvent(false);
            AsyncWaitHandle = resetEvent;
            executionType.Invoke(threadManager, () =>
                                                {
                                                    try
                                                    {
                                                        Result = action(AsyncState);
                                                        Status = OperationStatus.Success;
                                                    }
                                                    catch (Exception exception)
                                                    {
                                                        Status = OperationStatus.Faulted;
                                                        Exception = exception;
                                                    }
                                                    finally
                                                    {
                                                        resetEvent.Set();
                                                    }
                                                });
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that's completed successfully with the specified result.
        /// </summary>
        /// <returns>
        ///     The successfully completed operation.
        /// </returns>
        /// <param name="result">The result to store into the completed operation.</param>
        /// <typeparam name="TResult">The type of the result returned by the operation.</typeparam>
        public static IOperationResult<TResult> FromResult<TResult>(TResult result)
        {
            return new OperationResult<TResult>(result);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that's completed with the specified exception.
        /// </summary>
        /// <returns>
        ///     The faulted operation.
        /// </returns>
        public static IOperationResult FromException(Exception exception)
        {
            return new OperationResult(exception);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that's completed with the specified exception.
        /// </summary>
        /// <returns>
        ///     The faulted operation.
        /// </returns>
        /// <typeparam name="TResult">The type of the result returned by the operation.</typeparam>
        public static IOperationResult<TResult> FromException<TResult>(Exception exception)
        {
            return new OperationResult<TResult>(exception);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult" /> that executes a delegate.
        /// </summary>
        /// <returns>
        ///     A <see cref="IOperationResult" /> that represents the operation.
        /// </returns>
        public static IOperationResult FromDelegate(Action<object> action,
            ExecutionType executionType = ExecutionType.None, IThreadManager threadManager = null)
        {
            return FromDelegate(action, null, executionType, threadManager);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult" /> that executes a delegate.
        /// </summary>
        /// <returns>
        ///     A <see cref="IOperationResult" /> that represents the operation.
        /// </returns>
        public static IOperationResult FromDelegate(Action<object> action, object asyncState,
            ExecutionType executionType, IThreadManager threadManager = null)
        {
            Should.NotBeNull(action, "action");
            if (threadManager == null)
                threadManager = GlobalSettings.DefaultThreadManager;
            return new OperationResult(o =>
            {
                action(o);
                return null;
            }, asyncState, executionType, threadManager);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that executes a delegate.
        /// </summary>
        /// <returns>
        ///     A <see cref="IOperationResult{T}" /> that represents the operation.
        /// </returns>
        public static IOperationResult<T> FromDelegate<T>(Func<object, T> resultFunc,
            ExecutionType executionType = ExecutionType.None, IThreadManager threadManager = null)
        {
            return FromDelegate(resultFunc, null, executionType, threadManager);
        }

        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that executes a delegate.
        /// </summary>
        /// <returns>
        ///     A <see cref="IOperationResult{T}" /> that represents the operation.
        /// </returns>
        public static IOperationResult<T> FromDelegate<T>(Func<object, T> resultFunc, object asyncState,
            ExecutionType executionType, IThreadManager threadManager = null)
        {
            if (threadManager == null)
                threadManager = GlobalSettings.DefaultThreadManager;
            return new OperationResult<T>(resultFunc, asyncState, executionType, threadManager);
        }

        #endregion
    }

    /// <summary>
    ///     Represents the operation result.
    /// </summary>
    public class OperationResult<T> : OperationResult, IOperationResult<T>
    {
        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected OperationResult()
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected internal OperationResult(T result)
            : base(result)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected internal OperationResult(Exception exception)
            : base(exception)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="OperationResult" /> class.
        /// </summary>
        protected internal OperationResult(Func<object, T> action, object asyncState, ExecutionType executionType,
            IThreadManager threadManager)
            : base(o => action(o), asyncState, executionType, threadManager)
        {
        }

        #endregion

        #region Implementation of IOperationResult<T>

        /// <summary>
        ///     Gets the result value of this operation.
        /// </summary>
        public new T Result
        {
            get { return (T)base.Result; }
            protected set { base.Result = value; }
        }

        /// <summary>
        ///     Begins wait for the operation to complete execution.
        /// </summary>
        /// <param name="callback">The specified callback.</param>
        public void BeginWait(Action<IOperationResult<T>> callback)
        {
            base.BeginWait(result => callback((IOperationResult<T>)result));
        }

        #endregion
    }

    /// <summary>
    ///     Represents the manual operation result.
    /// </summary>
    public class ManualOperationResult<T> : OperationResult<T>
    {
        #region Fields

        private readonly object _locker;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ManualOperationResult{T}" /> class.
        /// </summary>
        public ManualOperationResult()
        {
            _locker = new object();
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Transitions the underlying <see cref="IOperationResult{T}" /> into the <see cref="OperationStatus.Success" />
        ///     state.
        /// </summary>
        /// <param name="result">The result value to bind to this <see cref="IOperationResult{T}" />.</param>
        /// <returns>
        ///     True if the operation was successful; otherwise, false.
        /// </returns>
        public void SetResult(T result)
        {
            if (!TrySetResult(result))
                Thrower.OperationResultIsNotEmpty();
        }

        /// <summary>
        ///     Transitions the underlying <see cref="IOperationResult{T}" /> into the <see cref="OperationStatus.Faulted" />
        ///     state.
        /// </summary>
        /// <param name="exception">The exception to bind to this <see cref="IOperationResult{T}" />.</param>
        public void SetException(Exception exception)
        {
            if (!TrySetException(exception))
                Thrower.OperationResultIsNotEmpty();
        }

        /// <summary>
        ///     Attempts to transition the underlying <see cref="IOperationResult{T}" /> into the
        ///     <see cref="OperationStatus.Success" /> state.
        /// </summary>
        /// <param name="result">The result value to bind to this <see cref="IOperationResult{T}" />.</param>
        /// <returns>
        ///     True if the operation was successful; otherwise, false.
        /// </returns>
        public bool TrySetResult(T result)
        {
            using (DeadLockDetector.Lock(_locker))
            {
                if (IsCompleted)
                    return false;
                Status = OperationStatus.Success;
                Result = result;
                return true;
            }
        }

        /// <summary>
        ///     Attempts to transition the underlying <see cref="IOperationResult{T}" /> into the
        ///     <see cref="OperationStatus.Faulted" /> state.
        /// </summary>
        /// <param name="exception">The exception to bind to this <see cref="IOperationResult{T}" />.</param>
        /// <returns>
        ///     True if the operation was successful; otherwise, false.
        /// </returns>
        public bool TrySetException(Exception exception)
        {
            Should.NotBeNull(exception, "exception");
            using (DeadLockDetector.Lock(_locker))
            {
                if (IsCompleted)
                    return false;
                Status = OperationStatus.Faulted;
                Exception = exception;
                return true;
            }
        }

        #endregion
    }
}